feat!: adapt to evmlib PaymentVault API and simplify pricing#56
feat!: adapt to evmlib PaymentVault API and simplify pricing#56
Conversation
Add unit and e2e tests covering the remaining Section 18 scenarios: Unit tests (32 new): - Quorum: #4 fail→abandoned, #16 timeout→inconclusive, #27 single-round dual-evidence, #28 dynamic threshold undersized, #33 batched per-key, #34 partial response unresolved, #42 quorum-derived paid-list auth - Admission: #5 unauthorized peer, #7 out-of-range rejected - Config: #18 invalid config rejected, #26 dynamic paid threshold - Scheduling: #8 dedup safety, #8 replica/paid collapse - Neighbor sync: #35 round-robin cooldown skip, #36 cycle completion, #38 snapshot stability mid-join, #39 unreachable removal + slot fill, #40 cooldown peer removed, #41 cycle termination guarantee, consecutive rounds, cycle preserves sync times - Pruning: #50 hysteresis prevents premature delete, #51 timestamp reset on heal, #52 paid/record timestamps independent, #23 entry removal - Audit: #19/#53 partial failure mixed responsibility, #54 all pass, #55 empty failure discard, #56 repair opportunity filter, response count validation, digest uses full record bytes - Types: #13 bootstrap drain, repair opportunity edge cases, terminal state variants - Bootstrap claims: #46 first-seen recorded, #49 cleared on normal E2e tests (4 new): - #2 fresh offer with empty PoP rejected - #5/#37 neighbor sync request returns response - #11 audit challenge multi-key (present + absent) - Fetch not-found for non-existent key Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates ant-node to match the refactored evmlib PaymentVault API by removing QuotingMetrics from quotes/candidate nodes, simplifying pricing to a quadratic function, and adjusting on-chain verification to the new contract surface.
Changes:
- Replace
QuotingMetricswith a singleprice: Amountacross quoting + merkle candidate flows, updating signing/verification and tests accordingly. - Switch payment verification to PaymentVault APIs (
verifyPayment,completedPayments) and update merkle verification to read completed merkle payment info + validate paid amounts. - Replace the previous pricing logic with a U256-based quadratic pricing formula and update devnet manifest wiring to a unified
payment_vault_address.
Reviewed changes
Copilot reviewed 9 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/payment/verifier.rs |
Adapts EVM + merkle payment verification to the unified PaymentVault API and new on-chain data structures. |
src/payment/single_node.rs |
Reworks SingleNode verification to use completedPayments and removes QuotingMetrics plumbing. |
src/payment/quote.rs |
Generates quotes/candidate nodes using price (derived from record count) and updates signature bytes accordingly. |
src/payment/pricing.rs |
Implements the new quadratic pricing formula in wei using Amount arithmetic and updates tests. |
src/payment/proof.rs |
Updates proof tests to construct quotes/candidates with price instead of QuotingMetrics. |
src/storage/handler.rs |
Adjusts merkle candidate quote request test expectations for the new candidate node structure. |
tests/e2e/merkle_payment.rs |
Updates E2E merkle payment test helpers to build/sign candidate nodes with price. |
src/devnet.rs |
Unifies devnet manifest EVM contract address fields into payment_vault_address. |
src/bin/ant-devnet/main.rs |
Emits unified vault address into the devnet manifest from the Anvil network config. |
Cargo.toml |
Points evmlib dependency to the refactor branch to pick up the new PaymentVault API. |
Cargo.lock |
Locks the new git-sourced evmlib resolution. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| //! Uses the formula `(close_records_stored / 6000)^2` to calculate storage price. | ||
| //! Integer division means nodes with fewer than 6000 records get a ratio of 0, | ||
| //! but a minimum floor of 1 prevents free storage. |
There was a problem hiding this comment.
The module docs claim that “integer division means nodes with fewer than 6000 records get a ratio of 0”, but calculate_price() scales before dividing (n² * 1e18 / 6000²), so prices for 1..5999 records are non-zero (and only n=0 hits the 1 wei floor). Please update the documentation to match the implemented formula/behavior.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 18 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 18 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 18 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 18 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Auto-scale: current DB footprint + available space − reserve. | ||
| let computed = compute_map_size(&env_dir, config.disk_reserve)?; | ||
| info!( | ||
| "Auto-computed LMDB map size: {:.2} GiB (available disk minus {:.2} GiB reserve)", |
There was a problem hiding this comment.
The info log says the computed map size is "available disk minus reserve", but compute_map_size() actually adds the current data.mdb file size back in (current_db_file_size + (available - reserve)). Consider adjusting the message to reflect that the value includes existing DB footprint, so operators can interpret the number correctly.
| "Auto-computed LMDB map size: {:.2} GiB (available disk minus {:.2} GiB reserve)", | |
| "Auto-computed LMDB map size: {:.2} GiB (existing DB size + available disk minus {:.2} GiB reserve)", |
| saorsa-pqc = "0.5" | ||
|
|
||
| # Payment verification - autonomi network lookup + EVM payment | ||
| evmlib = "0.5.0" | ||
| evmlib = "0.7" | ||
| xor_name = "5" |
There was a problem hiding this comment.
PR description notes evmlib is still on a local path and must be updated before merge, but Cargo.toml now points to the published evmlib = "0.7". Please update the PR description (or revert the dependency change if unintended) so the stated merge requirements match the actual diff.
The merkle payment verifier only checked that paid amounts were non-zero, not that they met the candidate's quoted price. A malicious client could submit fake low prices in PoolCommitment candidates while keeping the real poolHash, causing the contract to charge almost nothing while nodes still accepted the proof. Replace `paid_amount.is_zero()` with `paid_amount < node.price` so each paid candidate must receive at least their ML-DSA-65 signed quoted price. Also fix existing unit tests that were missing the Amount field in paid_node_addresses tuples, and add test_merkle_underpayment_rejected. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate from the old QuotingMetrics-based pricing and split DataPayments/MerklePayments contracts to the unified PaymentVault API in evmlib. Key changes: - Replace QuotingMetrics with a single `price: Amount` field on quotes - Replace logarithmic pricing with simple quadratic formula (n/6000)² - Unify data_payments_address + merkle_payments_address into payment_vault_address - Verify payments via completedPayments mapping instead of verify_data_payment batch call Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-payment-vault-v2 branch
The verifier checked `paid_amount >= node.price` (individual quote) but the contract pays each winner `median16(quotes) * 2^depth / depth`. A winner quoting above the median could be paid less than their quote, causing the node to incorrectly reject a valid payment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integrate `SingleNodePayment::from_quotes` to derive correct on-chain payment amounts. This ensures exact-match checks in the contract's `verifyPayment` function pass by reconstructing amounts as used by the client.
Nodes should store as many chunks as disk allows. The LMDB map size (db_size_gb) remains as the only storage bound. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nodes no longer advertise or track a maximum record count. The QuotingMetricsTracker API is simplified to only take initial_records. The evmlib QuotingMetrics::max_records field is set to 0 since we cannot remove it from the external type. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ics) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, verify() checked each node's own on-chain payment amount. Non-median nodes expected 0 and saw 0, so 4/5 nodes couldn't detect underpayment. Now all nodes check the single median quote's on-chain amount against the expected 3× price. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the fixed 32 GiB LMDB map ceiling with dynamic sizing that adapts to the storage partition. Nodes can now use all available disk space for chunks without an artificial cap. - Startup: map_size = current_db_size + available_space − reserve - Resize-on-demand: when MDB_MAP_FULL is hit (e.g. operator added storage), re-query disk space, resize map, retry the write - Disk-space guard: refuse writes when available space drops below a configurable reserve (default 1 GiB), with a 5-second TTL cache to avoid a statvfs syscall on every put - Safety: resize never shrinks below env.info().map_size so existing data is never at risk - New config: storage.disk_reserve_gb (default 1) - Operator override: storage.db_size_gb > 0 still imposes a hard cap Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the batch verifyPayment(DataPayment[5]) contract call with a single completedPayments(quoteHash) lookup on just the median quote, verifying it was paid at least 3x its price on-chain. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion When multiple quotes share the median price, stable sort ordering may differ between client and verifier, causing the verifier to check the wrong quote hash on-chain. Now checks all tied quotes and accepts if any one was paid correctly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…wards address generation Add `#[allow(clippy::cast_possible_truncation)]` to clarify safe use of cast, as `i` is always < 5.
…trained runners LmdbStorageConfig::default() sets disk_reserve to 1 GiB, which makes tests depend on the host having that much free space. Add a test_default() constructor with disk_reserve=0 and use it in all test/e2e configs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…empotency Storing an already-present chunk could fail with "Insufficient disk space" because the reserve check ran before the fast-path duplicate return. Since duplicates don't write anything, the guard is unnecessary for them. Moving it after the exists() check keeps duplicate puts as harmless no-ops. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
432c9a5 to
c3e136f
Compare
…unks removal The `max_chunks` field was removed from `LmdbStorageConfig` but the test helper in `audit.rs` still referenced it, causing a compilation failure. Replace with `disk_reserve: 0` to match the current struct definition. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nodes on smaller disks benefit from the reduced reserve while still retaining enough headroom for OS operations, logs, and temp files. - Rename config field `disk_reserve_gb` → `disk_reserve_mb` for finer granularity - Extract `DEFAULT_DISK_RESERVE` and `MIB`/`GIB` byte constants - Expose `storage::lmdb` as `pub(crate)` so `node.rs` can reuse `MIB` Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 20 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// A test-friendly default with `disk_reserve` set to 0 so unit tests | ||
| /// don't depend on the host having >= 1 GiB free disk space. | ||
| #[cfg(any(test, feature = "test-utils"))] | ||
| #[must_use] | ||
| pub fn test_default() -> Self { | ||
| Self { | ||
| disk_reserve: 0, | ||
| ..Self::default() |
There was a problem hiding this comment.
The test_default() doc says tests otherwise depend on the host having ">= 1 GiB" free disk space, but the default disk_reserve is 500 MiB (and map sizing is independent of free space). Consider rewording this to accurately describe the dependency you're removing (i.e., bypassing the disk_reserve guard during tests).
Update payment workflows, tests, and comments to reflect the new group size. Adjusted median calculation logic and related constants accordingly.
Summary
QuotingMetricswith a singleprice: Amountfield onPaymentQuoteandMerklePaymentCandidateNode, matching the updated evmlib APIdata_payments_address+merkle_payments_addressinto a singlepayment_vault_address(DevnetEvmInfo, CLI args)(n / 6000)²in wei — removes f64 arithmetic, uses U256 throughoutcompletedPaymentsmapping directly instead of the removedverify_data_paymentbatch callzero_quoting_metrics()helper and allQuotingMetricsconstruction in testsTest plan
cargo testpasses locally against local evmlib🤖 Generated with Claude Code